package com.question.aspect;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.question.annotation.Log;
import com.question.context.BaseContext;
import com.question.enumeration.MethodType;
import com.question.enumeration.OperatorType;
import com.question.mapper.UserMapper;
import com.question.pojo.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 记录操作日志AOP
 */
@Aspect
@Component
@Slf4j
public class LogAspect {

    @Autowired
    private UserMapper userMapper;

    @Value("${system.log-path:log/operation.log}")
    private String logFilePath;

    // 使用线程池异步写入日志
    private final ExecutorService logExecutor = Executors.newSingleThreadExecutor();

    /**
     * 计算操作消耗时间
     */
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");

    // 日期格式器
    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

    @Pointcut("@annotation(com.question.annotation.Log)")
    public void logPointcut(){}


    // 执行前操作 记录操作开始时间
    @Before("logPointcut()")
    public void doBefore(JoinPoint joinPoint){
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    /**
     * 操作正常返回时
     * @param joinPoint
     * @param log
     * @param jsonResult
     */
    @AfterReturning(pointcut = "logPointcut() && @annotation(log)",returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint,Log log,Object jsonResult){
        this.handleLog(joinPoint,log,null,jsonResult);
    }

    /**
     * 在方法抛出异常后，记录异常信息。
     * 调用 handleLog 方法，将日志信息保存到数据库
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
        handleLog(joinPoint, controllerLog, e, null);
    }

    // 将日志导出到log文件中
    protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
        log.info("自动导出日志已生效....");

        try {

            // 方法结束时间
            Long costTime = System.currentTimeMillis() - TIME_THREADLOCAL.get();

            // 操作者id
            Long userId = BaseContext.getCurrentId();
            // 用户名
            User user = userMapper.getById(userId);
            String username = user.getUsername();

            // 模块名
            String title = controllerLog.title();
            // 操作类型
            MethodType type = controllerLog.type();
            // 操作人角色
            OperatorType operatorType = controllerLog.operatorType();

            // 类名 方法名
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName(); //方法名
            String method =className + "." + methodName + "()"; // 包名加方法名

            // 参数
            String params = JSONUtil.toJsonStr(joinPoint.getArgs());

            boolean isSuccess = e == null;
            String status = isSuccess? "成功" : "失败";
            String errorMsg = isSuccess ? "" : (" | 异常信息: " + e.getMessage());

            // 构建日志内容
            StringBuilder logContent = new StringBuilder();

            logContent.append("[").append(LocalDateTime.now().format(DATE_FORMAT)).append("] "); // 当前时间
            logContent.append("用户id： ").append(userId).append(" | ");
            logContent.append("用户名： ").append(username).append(" | ");
            logContent.append("操作人角色： ").append(operatorType).append(" | ");
            logContent.append("操作模块： ").append(title).append(" | ");
            logContent.append("操作类型: ").append(type).append(" | ");
            logContent.append("操作方法: ").append(method).append(" | ");
            logContent.append("参数: ").append(StrUtil.subWithLength(params, 0, 500)).append(" | ");
            logContent.append("耗时: ").append(costTime).append("ms").append(" | ");
            logContent.append("状态: ").append(status).append(" | ");
            // 如果是失败操作，添加错误信息
            if (!isSuccess) {
                logContent.append(" | 错误: ").append(e.getMessage());
            }

            // 如果有返回结果且操作成功，添加结果信息
            if (isSuccess && jsonResult != null) {
                String result = JSONUtil.toJsonStr(jsonResult);
                logContent.append(" | 结果: ").append(StrUtil.subWithLength(result, 0, 500));
            }


            writeToFile(logContent.toString());

        }catch (Exception exp) {
            // 记录本地异常日志
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        } finally {
            //清理ThreadLocal数据
            TIME_THREADLOCAL.remove();
        }
    }

    // 异步写入文档中
    private void writeToFile(String content){

        try {
            // 获取项目根目录路径
            String projectRoot = System.getProperty("user.dir");
            Path logDir = Paths.get(projectRoot, "logs");

            // 确保日志目录存在
            if (!Files.exists(logDir)) {
                Files.createDirectories(logDir);
            }

            // 构建完整文件路径
            Path filePath = logDir.resolve("operation.log");

            // 使用try-with-resources自动关闭资源
            try (BufferedWriter writer = Files.newBufferedWriter(
                    filePath,
                    StandardCharsets.UTF_8,
                    StandardOpenOption.CREATE,
                    StandardOpenOption.APPEND)) {
                writer.write(content);
                writer.newLine();
            }

            log.info("日志成功写入: {}", filePath.toAbsolutePath());
        }catch (IOException e) {
            log.error("写入日志文件失败", e);
        }
    }

}
